import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;

import org.jbox2d.collision.CircleDef;
import org.jbox2d.collision.CircleShape;
import org.jbox2d.collision.Shape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.World;
import org.jbox2d.dynamics.joints.RevoluteJoint;
import org.jbox2d.dynamics.joints.RevoluteJointDef;

import pulpcore.Input;
import pulpcore.animation.Easing;
import pulpcore.animation.Fixed;
import pulpcore.animation.Int;
import pulpcore.image.BlendMode;
import pulpfizz.physics.Actor;
import pulpfizz.physics.BodyUtils;
import pulpfizz.physics.CollisionUtils;
import pulpfizz.physics.JointUtils;
import pulpfizz.physics.Material;
import pulpfizz.physics.PolygonUtils;
import pulpfizz.pulp.body.BodyDrawer;
import pulpfizz.pulp.body.Java2DBodySprite;
import pulpfizz.pulp.body.PhysicsLayer;
import pulpfizz.pulp.body.ShapeBodyDrawer;

public class Player extends Actor implements BodyDrawer
{
	public static final int CATEGORY = 0x2;

	Java2DBodySprite sprite;
	PhysicsLayer physics;

	/*
	 * Bodies and stuff.
	 */
	RevoluteJoint joint;
	Body mainBody;
	Body turretBody;
	CircleShape turretPoint;
	RevoluteJoint turretJoint;
	Shape sensor;
	
	/*
	 * Drawing shit.
	 */
	Color color = Color.WHITE;
	Area mainArea;
	Area turretArea;
	
	/*
	 * Force stuff.
	 */
	float maxMovementForce;
	Fixed force = new Fixed();
	Fixed turretForce = new Fixed();
	Fixed[] forces = new Fixed[] { force, turretForce };

	/*
	 * Bullet stuff.
	 */
	float babySize;
	float babyStepSize;
	float babyStartSize;
	float maxBabySize;
	float startingAmmo;
	float ammo;
	
	/*
	 * Keyboard control constants.
	 */
	int KEY_MOVE_LEFT;
	int KEY_MOVE_RIGHT;
	int KEY_HOLD;
	int KEY_SHOOT;
	private float size;

	/*
	 * Game stuff.
	 */
	public Int score = new Int();
	
	public Player(PhysicsLayer physics, float radius, float rad, float angle)
	{
		super();

		this.size = rad;
		startingAmmo = 12f*rad;
		ammo = startingAmmo;
		babyStepSize = 0.02f*rad;
		babyStartSize = 0.2f*rad;
		maxBabySize = 2f*rad;
		
		this.physics = physics;

		// Create the main body.
		mainBody = createBody(physics, angle);
		mainBody.setXForm(new Vec2(radius * (float) Math.cos(angle), radius * (float) Math.sin(angle)), angle
				- (float) Math.PI / 2); 
		
		createAreas();
		
		maxMovementForce = mainBody.getMass() * 20 * 2700;

		// Create the Java2DBodySprite
		// Note that we have to do this ugly subclassing to allow ourselves to get updated each frame.
		sprite = new Java2DBodySprite(mainBody, physics,2)
		{
			@Override
			public void update(int elapsedTime)
			{
				super.update(elapsedTime);
				step(elapsedTime);
			}
		};
		sprite.setPadding(20);
		sprite.loadImages();
		sprite.setBodyDrawer(this);
		sprite.alpha.set(220);
//		sprite.setBlendMode(BlendMode.Add());
		physics.add(sprite);

		// Create the rotation joint.
		World w = physics.getWorld();
		Body groundBody = w.getGroundBody();

		RevoluteJointDef jd = new RevoluteJointDef();
		jd.body1 = groundBody;
		jd.body2 = mainBody;
		jd.localAnchor1 = groundBody.getLocalCenter();
		jd.localAnchor2 = mainBody.getLocalPoint(groundBody.getWorldCenter());
		joint = (RevoluteJoint) w.createJoint(jd);

		/*
		 * Create the turret mainBody and joint.
		 */
		Vec2 v = mainBody.getWorldCenter();
		turretBody = BodyUtils.createCircle(w, v.x, v.y - rad*1.25f, rad / 2f);
		CircleDef cd = new CircleDef();
		cd.localPosition = new Vec2(0, +rad*1.25f);
		cd.radius = rad / 2;
		turretPoint = (CircleShape) turretBody.createShape(cd);
		Material m = Material.WOOD();
		m.setDensity(0.1f);
		m.applyAll(turretBody);

		// We probably want these guys to have CCD turned on.
		turretBody.setBullet(true);
		mainBody.setBullet(true);

		turretJoint = JointUtils.revoluteJoint(mainBody, turretBody, mainBody.getWorldCenter());
		Java2DBodySprite jbs = new Java2DBodySprite(turretBody, physics,2);
		jbs.setBodyDrawer(new ShapeBodyDrawer(turretArea){
			@Override
			public void drawBody(PhysicsLayer physics, Body b, Graphics2D g2)
			{
				// Draw the turret.
				g2.setPaint(color);
				g2.setStroke(new BasicStroke(1/physics.getScaling()*1));
				g2.draw(shape);
				
				// Draw the baby.
				float rad = Bullet.getRadForSize(babySize);
				babyE.setFrameFromCenter(0,0,-rad,-rad);
				g2.setStroke(new BasicStroke(1/physics.getScaling()*2));
				g2.setPaint(new Color(200,255,200));
				g2.draw(babyE);
			}
			@Override
			public boolean needsRedraw()
			{
				return true;
			}
		});
		jbs.setPadding(10);
		jbs.setBlendMode(BlendMode.Add());
		jbs.alpha.set(220);
		physics.add(jbs);
		
		// Create the actor.
		addBody(mainBody);
		addBody(turretBody);
		setName("Player");
		addToGroup(TwoPlayerScene.PLAYER_GROUP);

		CollisionUtils.setCategory(Player.CATEGORY, mainBody, turretBody);
		CollisionUtils.setCategory(0x1, sensor);
	}

	private void createAreas()
	{
		Ellipse2D ellipse = new Ellipse2D.Float();
		ellipse.setFrameFromCenter(0, 0, -size, -size);
		Ellipse2D e2 = new Ellipse2D.Float();
		float wh = size * 1.25f;
		e2.setFrame(-wh, -wh/5, wh*2,wh*2);
		
		mainArea = new Area(ellipse);
		mainArea.subtract(new Area(e2));
		
		turretArea = (Area) mainArea.clone();
		turretArea = turretArea.createTransformedArea(AffineTransform.getScaleInstance(0.25,0.25));
//		turretArea = turretArea.createTransformedArea(AffineTransform.getTranslateInstance(0,-size*1.5f));
	}
	
	private Body createBody(PhysicsLayer physics, float angle)
	{
		World w = physics.getWorld();
		Ellipse2D ellipse = new Ellipse2D.Float();
		ellipse.setFrameFromCenter(new Point2D.Float(0, 0), new Point2D.Float(-size, -size));
		Body b = PolygonUtils.areaToBody(physics, ellipse, size / 10);

		Material m = Material.WOOD();
		m.setRestitution(0);
		m.setDensity(m.getDensity() * .1f);
		m.applyAll(b);

		CircleDef c = new CircleDef();
		c.radius = size;
		c.isSensor = true;
		sensor = b.createShape(c);

		return b;
	}

	public void setControls(int left, int right, int hold, int shoot)
	{
		KEY_MOVE_LEFT = left;
		KEY_MOVE_RIGHT = right;
		KEY_HOLD = hold;
		KEY_SHOOT = shoot;
	}

	private int lastShotTime;

	public boolean disabled;

	public void setDisabled(boolean disabled)
	{
//		System.out.println(getName() + "  " + disabled);
		this.disabled = disabled;
	}

	public boolean isDisabled()
	{
		return disabled;
	}

	public void step(int elapsedTime)
	{
		force.update(elapsedTime);
		turretForce.update(elapsedTime);

		if (Input.isReleased(KEY_HOLD))
		{
			turretForce.set(0);
			if (!Input.isDown(KEY_MOVE_LEFT) && !Input.isDown(KEY_MOVE_RIGHT))
				force.set(0);
		}
		
		if (Input.isPressed(KEY_MOVE_LEFT))
		{
			for (Fixed f : forces)
			{
				if (f.get() < 0)
					f.set(0);
				f.animateTo(maxMovementForce, 1200, Easing.NONE);
			}

		} else if (Input.isPressed(KEY_MOVE_RIGHT))
		{
			for (Fixed f : forces)
			{
				if (f.get() > 0)
					f.set(0);
				f.animateTo(-maxMovementForce, 1200, Easing.NONE);
			}
		}
		if (!(Input.isDown(KEY_MOVE_LEFT) || Input.isDown(KEY_MOVE_RIGHT)))
		{
			for (Fixed f : forces)
			{
				if (f == force)
					f.animateTo(0, 200);
				else
					f.animateTo(0, 20);
			}
		}

		if (Input.isPressed(KEY_HOLD))
		{
			mainBody.setAngularVelocity(0);
			turretBody.setAngularVelocity(0);
			mainBody.setLinearVelocity(new Vec2());
			turretBody.setLinearVelocity(new Vec2());
			turretForce.set(0);
			force.set(0);
		}

		if (Input.isDown(KEY_HOLD))
		{
			turretJoint.enableLimit(true);
			turretJoint.setLimits((float) -Math.PI / 2.5f, (float) Math.PI / 2.5f);
			turretBody.setAngularVelocity(turretBody.getAngularVelocity() * .6f);
			turretBody.applyTorque((float) turretForce.get() * turretBody.getMass() / 250);
		} else
		{
			turretJoint.enableLimit(true);
			turretJoint.setLimits(0, 0);
			applyTangentialForce(mainBody, -force.get() * mainBody.getMass(), 0);
		}

		mainBody.setLinearVelocity(mainBody.getLinearVelocity().mul(0.25f));

		if (Input.isDown(KEY_SHOOT) && !isDisabled())
		{
			if (ammo > babySize + babyStepSize)
			{
				babySize += babyStepSize;
			} else
			{
				babySize = ammo;
			}
			if (babySize > maxBabySize)
				babySize = maxBabySize;
		}

		if (Input.isPressed(KEY_SHOOT) && !isDisabled())
		{
			if (ammo > babyStartSize)
			{
				babySize = babyStartSize;
			} else
			{
				babySize = ammo;
			}
		}

		if (Input.isReleased(KEY_SHOOT))
		{
			if (!isDisabled())
			{
				shoot();
			} else
			{
				babySize = 0;
			}
		}
	}

	void applyTangentialForce(Body b, double f, double forceAngle)
	{
		float angle = b.getAngle();
		Vec2 forceV = new Vec2((float) Math.cos(angle + forceAngle), (float) Math.sin(angle + forceAngle));
		forceV = forceV.mul((float) f);
		b.applyForce(forceV, b.getWorldCenter());
	}

	public void grabBullet(Bullet b)
	{
		long l = System.currentTimeMillis();
		if (l > b.validGrabTime)
		{
			physics.remove(b.sprite);
			mainBody.getWorld().destroyBody(b.body);
			ammo += b.size;
		}
	}

	public void shoot()
	{
		if (ammo > 0 && babySize > 0)
		{
			Bullet b = new Bullet(physics, babySize, this);
			ammo -= b.size;
			babySize = 0;
			
			// Get the vector that runs through the turret and our main mainBody.
			Vec2 dV = turretBody.getWorldVector(turretPoint.getLocalPosition());
			dV.normalize();
			dV.mulLocal(-1);

			b.body.setXForm(turretBody.getPosition(), 0);

//			dV.mulLocal(b.body.getMass());
			dV.mulLocal(15);

			//			dV.mulLocal(2);

			//			Vec2 vV = mainBody.getLinearVelocityFromLocalPoint(turretPoint.getLocalPosition());
			//			dV.add(vV.mulLocal(2));
			b.body.setLinearVelocity(dV);
//			b.body.applyImpulse(dV.clone(), b.body.getWorldCenter());
		} else
		{
			//TODO: Play an "empty gun" sound...
		}
	}

	Ellipse2D ammoE = new Ellipse2D.Float();
	Ellipse2D babyE = new Ellipse2D.Float();
	public void drawBody(PhysicsLayer physics, Body b, Graphics2D g2)
	{
		// Draw ourselves.
		g2.setPaint(color);
		g2.setStroke(new BasicStroke(1/ physics.getScaling()*2));
		g2.draw(mainArea);
		
		// Draw our ammo.
		g2.setPaint(color);
		g2.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_OVER, 0.5f));
		float ammoMult = ammo / startingAmmo * .5f;
		ammoE.setFrame(-ammoMult/2, size/8, ammoMult,ammoMult);
		g2.fill(ammoE);
	}

	public boolean needsRedraw()
	{
		return true;
	}

}
